﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using K9Nano.Dependency;
using K9Nano.Domain;
using K9Nano.Share.Exceptions;
using Microsoft.Extensions.Logging;

namespace K9Nano.Organization
{
    /*
     * From ABP
     */
    public class OrganizationUnitManager : K9DomainServiceBase, IScopedDependency
    {
        protected IRepository<OrganizationUnit, Guid> OrganizationUnitRepository { get; }

        public OrganizationUnitManager(IUnitOfWork uow, ILoggerFactory loggerFactory) : base(uow, loggerFactory)
        {
            OrganizationUnitRepository = Uow.Repository<OrganizationUnit, Guid>();
        }

        public virtual async Task<OrganizationUnit> GetAsync(Guid id, CancellationToken cancellation)
        {
            return await OrganizationUnitRepository.GetAsync(id, cancellation);
        }

        public virtual async Task CreateAsync(OrganizationUnit organizationUnit, CancellationToken cancellation)
        {
            organizationUnit.Code = await GetNextChildCodeAsync(organizationUnit.ParentId, cancellation);
            await ValidateOrganizationUnitAsync(organizationUnit, cancellation);
            await OrganizationUnitRepository.InsertAsync(organizationUnit, cancellation);
        }

        public virtual async Task UpdateAsync(OrganizationUnit organizationUnit, CancellationToken cancellation)
        {
            await ValidateOrganizationUnitAsync(organizationUnit, cancellation);
            await OrganizationUnitRepository.UpdateAsync(organizationUnit, cancellation);
        }

        public virtual async Task<string> GetNextChildCodeAsync(Guid? parentId, CancellationToken cancellation)
        {
            var lastChild = await GetLastChildOrNullAsync(parentId, cancellation);
            if (lastChild == null)
            {
                var parentCode = parentId != null ? await GetCodeAsync(parentId.Value, cancellation) : null;
                return OrganizationUnit.AppendCode(parentCode, OrganizationUnit.CreateCode(1));
            }

            return OrganizationUnit.CalculateNextCode(lastChild.Code);
        }

        public virtual async Task<OrganizationUnit> GetLastChildOrNullAsync(Guid? parentId, CancellationToken cancellation)
        {
            var children = await OrganizationUnitRepository.GetAllListAsync(ou => ou.ParentId == parentId, cancellation);
            return children.OrderBy(c => c.Code).LastOrDefault();
        }

        public virtual async Task<string> GetCodeAsync(Guid id, CancellationToken cancellation)
        {
            var ou = await OrganizationUnitRepository.GetAsync(id, cancellation);
            if (ou == null)
            {
                throw new K9UserFriendlyException($"ID为{id}的组织机构不存在");
            }
            return ou.Code;
        }

        public virtual async Task DeleteAsync(Guid id, CancellationToken cancellation)
        {
            var children = await FindChildrenAsync(id, true, cancellation);

            foreach (var child in children)
            {
                await OrganizationUnitRepository.DeleteAsync(child, cancellation: cancellation);
            }

            await OrganizationUnitRepository.DeleteAsync(id, cancellation: cancellation);
        }

        public virtual async Task MoveAsync(Guid id, Guid? parentId, CancellationToken cancellation)
        {
            var organizationUnit = await OrganizationUnitRepository.GetAsync(id, cancellation);
            if (organizationUnit.ParentId == parentId)
            {
                return;
            }

            //Should find children before Code change
            var children = await FindChildrenAsync(id, true, cancellation);

            //Store old code of OU
            var oldCode = organizationUnit.Code;

            //Move OU
            organizationUnit.Code = await GetNextChildCodeAsync(parentId, cancellation);
            organizationUnit.ParentId = parentId;

            await ValidateOrganizationUnitAsync(organizationUnit, cancellation);

            await Uow.RunTransactionAsync(async () =>
            {
                //Update Children Codes
                foreach (var child in children)
                {
                    child.Code = OrganizationUnit.AppendCode(organizationUnit.Code,
                        OrganizationUnit.GetRelativeCode(child.Code, oldCode));
                    await OrganizationUnitRepository.UpdateAsync(child, default);
                }

                await OrganizationUnitRepository.UpdateAsync(organizationUnit, default);
            });
        }

        public async Task<List<OrganizationUnit>> FindChildrenAsync(Guid? parentId, bool recursive, CancellationToken cancellation)
        {
            if (!recursive)
            {
                return await OrganizationUnitRepository.GetAllListAsync(ou => ou.ParentId == parentId, cancellation);
            }

            if (!parentId.HasValue)
            {
                return await OrganizationUnitRepository.GetAllListAsync(cancellation);
            }

            var code = await GetCodeAsync(parentId.Value, cancellation);

            return await OrganizationUnitRepository.GetAllListAsync(
                ou => ou.Code.StartsWith(code) && ou.Id != parentId.Value, cancellation);
        }

        protected virtual async Task ValidateOrganizationUnitAsync(OrganizationUnit organizationUnit, CancellationToken cancellation)
        {
            bool invalid;
            if (organizationUnit.ParentId.HasValue)
            {
                invalid = await OrganizationUnitRepository
                    .AnyAsync(x => x.DisplayName == organizationUnit.DisplayName, cancellation);
            }
            else
            {
                invalid = await OrganizationUnitRepository
                    .AnyAsync(a => a.ParentId == organizationUnit.ParentId
                                   && a.Id != organizationUnit.Id
                                   && a.DisplayName == organizationUnit.DisplayName, cancellation);
            }

            if (invalid)
            {
                throw new K9UserFriendlyException("组织机构重复: " + organizationUnit.DisplayName);
            }
        }
    }
}